package com.hnit.applet.common.handler;

import com.hnit.applet.common.config.AppUtil;
import com.hnit.applet.common.constant.CommonConstant;
import com.hnit.applet.common.constant.MessageConstant;
import com.hnit.applet.common.enums.ResponseStatus;
import com.hnit.applet.common.exception.AppRuntimeException;
import com.hnit.applet.common.model.Error;
import com.hnit.applet.common.utils.MessageSourceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;

/**
 * @author zwj
 * @date 2022/7/13
 * @description TODO 全局异常处理
 */
@ControllerAdvice
@Slf4j
public class AppRuntimeExceptionHandler {

    @Autowired
    protected MessageSourceUtil messageSource;

    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Error> handleAppRuntimeException(Exception e, HttpServletRequest request) {
        if (e instanceof AppRuntimeException) {
            return handler((AppRuntimeException) e);
        } else if (e instanceof ConstraintViolationException) {
            return handler((ConstraintViolationException) e);
        } else if (e instanceof AuthenticationException) {
            return handler((AuthenticationException) e);
        } else if (e instanceof RuntimeException) {
            return handler((RuntimeException) e, request);
        } else {
            return handler(e);
        }
    }

    private ResponseEntity<Error> handler(AuthenticationException e) {
        log.error(AppUtil.printFullExceptionStack(e));
        Error error = new Error().toBuilder().code(MessageConstant.ErrorCode.Common.COMMON_ERR_L_ILLEGAL_TOKEN)
                .message(messageSource.getMessage(MessageConstant.ErrorCode.Common.COMMON_ERR_L_ILLEGAL_TOKEN))
                .details(e.getMessage()).build();
        return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED);
    }

    private ResponseEntity<Error> handler(ConstraintViolationException e) {
        log.error(AppUtil.printFullExceptionStack(e));
        Error error = new Error().toBuilder().code(MessageConstant.ErrorCode.Common.COMMON_ERR_L_CONSTRAINT_VIOLATION)
                .message(messageSource.getMessage(MessageConstant.ErrorCode.Common.COMMON_ERR_L_CONSTRAINT_VIOLATION))
                .details(e.getMessage()).build();
        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
    }


    private ResponseEntity<Error> handler(AppRuntimeException e) {
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
        try {
            httpStatus = HttpStatus.valueOf(ResponseStatus.valueOf(e.getStatusCode()).value());
        } catch (RuntimeException e1) {
            log.error(AppUtil.printFullExceptionStack(e1));
        }

        StringBuilder buffer = new StringBuilder(AppUtil.printFullExceptionStack(e));
        log.error(buffer.toString());
        Error error = new Error().toBuilder().code(e.getCode()).message(e.getMessage()).build();
        if (log.isDebugEnabled()) {
            error.setDetails(buffer.toString());
            return new ResponseEntity<>(error, httpStatus);
        }
        return new ResponseEntity<>(error, httpStatus);
    }

    private ResponseEntity<Error> handler(RuntimeException e, HttpServletRequest request) {
        if (e.getMessage().contains(CommonConstant.Exception.FLOW_EXCEPTION)) {
            log.error(AppUtil.printFullExceptionStack(e));
            Error error = new Error().toBuilder().code(MessageConstant.ErrorCode.Sentinel.SENTINEL_ERR_L_FLOW)
                    .message(messageSource.getMessage(MessageConstant.ErrorCode.Sentinel.SENTINEL_ERR_L_FLOW,
                            new Object[]{request.getRemoteHost()}))
                    .details(e.getMessage()).build();
            return new ResponseEntity<>(error, HttpStatus.TOO_MANY_REQUESTS);
        } else if (e.getMessage().contains(CommonConstant.Exception.DEGRADE_EXCEPTION)) {
            log.error(AppUtil.printFullExceptionStack(e));
            Error error = new Error().toBuilder().code(MessageConstant.ErrorCode.Sentinel.SENTINEL_ERR_L_DEGRADE)
                    .message(messageSource.getMessage(MessageConstant.ErrorCode.Sentinel.SENTINEL_ERR_L_DEGRADE,
                            new Object[]{request.getRemoteHost()}))
                    .details(e.getMessage()).build();
            return new ResponseEntity<>(error, HttpStatus.TOO_MANY_REQUESTS);
        } else if (e.getMessage().contains(CommonConstant.Exception.PARAM_FLOW_EXCEPTION)) {
            log.error(AppUtil.printFullExceptionStack(e));
            Error error = new Error().toBuilder().code(MessageConstant.ErrorCode.Sentinel.SENTINEL_ERR_L_PARAM_FLOW)
                    .message(messageSource.getMessage(MessageConstant.ErrorCode.Sentinel.SENTINEL_ERR_L_PARAM_FLOW,
                            new Object[]{request.getRemoteHost()}))
                    .details(e.getMessage()).build();
            return new ResponseEntity<>(error, HttpStatus.TOO_MANY_REQUESTS);
        } else if (e.getMessage().contains(CommonConstant.Exception.SYSTEM_BLOCK_EXCEPTION)) {
            log.error(AppUtil.printFullExceptionStack(e));
            Error error = new Error().toBuilder().code(MessageConstant.ErrorCode.Sentinel.SENTINEL_ERR_L_SYSTEM_BLOCK)
                    .message(messageSource.getMessage(MessageConstant.ErrorCode.Sentinel.SENTINEL_ERR_L_SYSTEM_BLOCK,
                            new Object[]{request.getRemoteHost()}))
                    .details(e.getMessage()).build();
            return new ResponseEntity<>(error, HttpStatus.TOO_MANY_REQUESTS);
        } else if (e.getMessage().contains(CommonConstant.Exception.AUTHORITY_EXCEPTION)) {
            log.error(AppUtil.printFullExceptionStack(e));
            Error error = new Error().toBuilder().code(MessageConstant.ErrorCode.Sentinel.SENTINEL_ERR_L_AUTHORITY)
                    .message(messageSource.getMessage(MessageConstant.ErrorCode.Sentinel.SENTINEL_ERR_L_AUTHORITY,
                            new Object[]{request.getRemoteHost()}))
                    .details(e.getMessage()).build();
            return new ResponseEntity<>(error, HttpStatus.TOO_MANY_REQUESTS);
        } else {
            return handler(e);
        }

    }

    private ResponseEntity<Error> handler(Exception e) {
        StringBuilder buffer = new StringBuilder(AppUtil.printFullExceptionStack(e));
        log.error(buffer.toString());

        Error error = new Error().toBuilder().code(MessageConstant.ErrorCode.Common.COMMON_ERR_L_UNKNOWN)
                .message(messageSource.getMessage(MessageConstant.ErrorCode.Common.COMMON_ERR_L_UNKNOWN)).build();
        if (log.isDebugEnabled()) {
            error.setDetails(buffer.toString());
        }

        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
